use crate::{Solution, SolutionPair};
use regex::{Error as RegexError, Regex};
use std::error::Error;
use std::fs::read_to_string;

fn parse_stacks(stacks: &str) -> Result<Vec<Vec<char>>, Box<dyn Error>> {
    let mut lines = stacks.lines().collect::<Vec<&str>>();
    lines.reverse();
    let stack_num = lines
        .first()
        .unwrap()
        .split(' ')
        .filter(|c| !c.is_empty())
        .count();

    let mut stacks = (0..)
        .take(stack_num)
        .map(|_| Vec::new())
        .collect::<Vec<Vec<char>>>();

    lines[1..].iter().for_each(|line| {
        let mut stack_index = 0;

        (*line).chars().enumerate().for_each(|(char_index, char)| {
            let index = char_index % 4;
            if index == 1 && char != ' ' {
                stacks[stack_index].push(char);
            } else if index == 3 {
                stack_index += 1;
            }
        });
    });

    Ok(stacks)
}

fn parse_move(move_text: &str) -> Result<(usize, usize, usize), Box<dyn Error>> {
    let move_regex = Regex::new(r"^move (\d+) from (\d+) to (\d+)$")?;
    let captures = move_regex
        .captures(move_text)
        .ok_or_else(|| RegexError::Syntax(String::from("capture error")))?;

    let move_item_num = captures[1].parse::<usize>()?;
    let source_stack = captures[2].parse::<usize>()?;
    let target_stack = captures[3].parse::<usize>()?;

    Ok((move_item_num, source_stack - 1, target_stack - 1))
}

fn solve_part1(input: &str) -> String {
    let input = input.split("\n\n").collect::<Vec<&str>>();
    let moves = input[1]
        .lines()
        .map(parse_move)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let mut stacks = parse_stacks(input[0]).unwrap();

    moves
        .into_iter()
        .for_each(|(move_item_num, source_stack_index, target_stack_index)| {
            for _ in (0..).take(move_item_num) {
                let source_stack = &mut stacks[source_stack_index];
                let current_crate = (*source_stack).pop().unwrap();
                let target_stack = &mut stacks[target_stack_index];
                target_stack.push(current_crate);
            }
        });

    stacks
        .into_iter()
        .map(|stack| *stack.last().unwrap_or(&' '))
        .collect::<String>()
}

fn solve_part2(input: &str) -> String {
    let input = input.split("\n\n").collect::<Vec<&str>>();
    let moves = input[1]
        .lines()
        .map(parse_move)
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let mut stacks = parse_stacks(input[0]).unwrap();

    moves
        .into_iter()
        .for_each(|(move_item_num, source_stack_index, target_stack_index)| {
            let source_stack = &mut stacks[source_stack_index];
            let mut drained_items = source_stack
                .drain((source_stack.len() - move_item_num)..)
                .collect::<Vec<char>>();

            let target_stack = &mut stacks[target_stack_index];
            target_stack.append(&mut drained_items);
        });

    stacks
        .into_iter()
        .map(|stack| *stack.last().unwrap_or(&' '))
        .collect::<String>()
}

pub fn solve() -> SolutionPair {
    let input = read_to_string("input/day05.txt").unwrap();

    let sol1 = solve_part1(&input);
    let sol2 = solve_part2(&input);

    (Solution::from(sol1), Solution::from(sol2))
}

#[cfg(test)]
mod test {
    use crate::etc::utils;

    use super::*;

    #[test]
    fn regex() {
        let re = Regex::new(r"(?i)rust").unwrap();
        assert!(re.is_match("I love RUST!"));
    }

    #[test]
    fn move_str() {
        dbg!(parse_move("move 1 from 2 to 3").unwrap());
    }

    #[test]
    fn part1() {
        let input = utils::to_multiline_string(vec![
            "    [D]    ",
            "[N] [C]    ",
            "[Z] [M] [P]",
            " 1   2   3 ",
            "",
            "move 1 from 2 to 1",
            "move 3 from 1 to 3",
            "move 2 from 2 to 1",
            "move 1 from 1 to 2",
        ]);
        assert_eq!("CMZ", solve_part1(&input));
    }

    #[test]
    fn part2() {
        let input = utils::to_multiline_string(vec![
            "    [D]    ",
            "[N] [C]    ",
            "[Z] [M] [P]",
            " 1   2   3 ",
            "",
            "move 1 from 2 to 1",
            "move 3 from 1 to 3",
            "move 2 from 2 to 1",
            "move 1 from 1 to 2",
        ]);
        assert_eq!("MCD", solve_part2(&input));
    }
}
